defmodule SockWorkstation.PanCouchbeamClient do
  use GenServer

  @max_retries 3
  @retry_delay 5_000
  @name __MODULE__

  @url "http://127.0.0.1:5984/"
  # @url "http://db.72-j.cn:4005/"

  defstruct conn: nil,
            url: nil,
            options: [],
            retry_count: 0,
            max_retries: @max_retries,
            retry_delay: @retry_delay

  # 客户端API
  def start_link(opts) do
    GenServer.start_link(@name, opts, name: @name)
  end

  def get_state() do
    GenServer.call(@name, :get_state)
  end

  def create_db(name) do
    GenServer.call(@name, {:create_db, name})
  end

  def get_db(name) do
    GenServer.call(@name, {:get_db, name})
  end

  def get_doc(db, id) do
    GenServer.call(@name, {:get_doc, db, id})
  end

  def get_value(db, doc, key) do
    GenServer.call(@name, {:get_value, db, doc, key})
  end

  def get_stream_attachment(db, doc_id, name) do
    GenServer.call(@name, {:get_stream_attachment, db, doc_id, name}, 30_000)
  end

  def save_doc(db, doc) do
    GenServer.call(@name, {:save_doc, db, doc})
  end

  def save_attachment(db, attachment) do
    GenServer.call(@name, {:save_attachment, db, attachment})
  end

  def save_attachment(db ,doc_id ,name, data) do
    GenServer.call(@name, {:save_attachment, db, doc_id ,name, data})
  end


  def replicate_db(source_db, target_db) do
    GenServer.call(@name, {:replicate_db, source_db, target_db}, 60_000)
  end

  # def upload_attachment(db, attachment) do
  #   GenServer.call(@name, {:upload_attachment, db, attachment})
  # end

  def set_value(db, doc, key, value) do
    GenServer.call(@name, {:set_value, db, doc, key, value})
  end

  def delete_value(db, doc, key) do
    GenServer.call(@name, {:delete_value, db, doc, key})
  end

  def update_value(db, doc, key, value) do
    GenServer.call(@name, {:update_value, db, doc, key, value})
  end

  def update_map(db, doc, map) do
    GenServer.call(@name, {:update_map, db, doc, map})
  end

  def update_attachment(db, doc, attachment) do
    GenServer.call(@name, {:update_attachment, db, doc, attachment})
  end

    def update_attachment(db, doc_id, name, data) do
    GenServer.call(@name, {:update_attachment, db, doc_id, name, data})
  end

  def tick_num(db, doc, key) do
    GenServer.call(@name, {:tick_num, db, doc, key})
  end

  def delete_doc(db, doc) do
    GenServer.call(@name, {:delete_doc, db, doc})
  end

  @impl true
  def init(opts) do
    Process.flag(:trap_exit, true)
    # IO.inspect(opts)

    state = %@name{
      url: @url || opts[:url],
      options: opts[:options] || []
    }

    connect(state)
    # {:ok, state}
    # case connect(state) do
    #   {:ok, state} -> {:ok, state}
    #   {:error, reason} -> {:stop, {:connection_failed, reason}}
    # end
  end

  defp connect(%@name{} = state) do
    conn = :couchbeam.server_connection(state.url, state.options)

    {:ok, %{state | conn: conn, retry_count: 0}}
  end

  @impl true
  def handle_call(:get_state, _from, state) do
    {:reply, state, state}
  end

  def handle_call({:create_db, name}, _from, state) do
    reply = :couchbeam.create_db(state.conn, name)
    {:reply, reply, state}
  end

  def handle_call({:get_db, name}, _from, state) do
    reply = :couchbeam.open_db(state.conn, name)
    {:reply, reply, state}
  end

  def handle_call({:get_doc, db, id}, _from, state) do
    reply =
      with {:ok, db} <- :couchbeam.open_db(state.conn, db) do
        :couchbeam.open_doc(db, id)
      end

    {:reply, reply, state}
  end

  def handle_call({:get_value, db, doc, key}, _from, state) do
    reply =
      with {:ok, db} <- :couchbeam.open_db(state.conn, db),
           {:ok, doc} <- :couchbeam.open_doc(db, doc) do
        # IO.puts("doc: #{inspect(doc)}")
        :couchbeam_doc.get_value(key, doc)
      end

    {:reply, reply, state}
  end

  def handle_call({:get_stream_attachment, db, doc, name}, _from, state) do
    {:ok, db} = :couchbeam.open_db(state.conn, db)
    reply =
    # with {:ok, db} <- :couchbeam.open_db(state.conn, db) do
      #  {:ok, doc} <- :couchbeam.open_doc(db, doc) do
      # task =
        # Task.async(fn ->
          case  :couchbeam.fetch_attachment(db, doc, name, [stream: true]) do
            {:ok, ref} ->
              # {:ok, file} = File.open("output.file", [:write, :binary])

              # 逐块读取附件内容
              :couchbeam.stream_attachment(ref)
              # Enum.each(stream, fn chunk ->
              #   IO.binwrite(file, chunk)
              # end)

              # 3. 关闭文件
              # File.close(file)
              # {:ok, "File saved successfully"}
            {:error, reason} ->
              IO.puts("Failed to initialize attachment stream: #{inspect(reason)}")
              {:error, reason}
          end
        # end)

      # 或手动管理回复
      # {:reply, Task.await(task, 30_000), state}
    # end

    {:reply, reply, state}
  end

  def handle_call({:set_value, db, doc, key, value}, _from, state) do
    reply =
      with {:ok, db} <- :couchbeam.open_db(state.conn, db),
           {:ok, doc} <- :couchbeam.open_doc(db, doc),
           doc <- :couchbeam_doc.set_value(key, value, doc) do
        :couchbeam.save_doc(db, doc)
      end

    {:reply, reply, state}
  end

  def handle_call({:update_value, db, doc, key, value}, _from, state) do
    reply =
      with {:ok, db} <- :couchbeam.open_db(state.conn, db),
           {:ok, doc} <- :couchbeam.open_doc(db, doc),
           doc <- :couchbeam_doc.set_value(key, value, doc) do
        :couchbeam.save_doc(db, doc)
      end

    {:reply, reply, state}
  end

  def handle_call({:update_map, db, doc, map}, _from, state) do
    reply =
      with {:ok, db} <- :couchbeam.open_db(state.conn, db),
           {:ok, doc} <- :couchbeam.open_doc(db, doc) do
        doc =
          Enum.reduce(map, doc, fn {key, value}, doc ->
            :couchbeam_doc.set_value(key, value, doc)
          end)

        :couchbeam.save_doc(db, doc)
      end

    {:reply, reply, state}
  end

  def handle_call({:replicate_db, source_db, target_db}, _from, state) do
    reply =
      :couchbeam.replicate(state.conn, source_db, target_db)

    {:reply, reply, state}
  end

  # def handle_call({:upload_attachment, db, attachment}, _from, state) do
  #   reply =
  #     with {:ok, db} <- :couchbeam.open_db(state.conn, db) do
  #       # file = File.open!(attachment.file_path, [:read, :binary])
  #       # stream = fn chunk ->
  #       #   case File.read(file, 8192) do
  #       #     {:ok, data} -> {:ok, data}
  #       #     :eof -> :eof
  #       #     {:error, reason} -> {:error, reason}
  #       #   end
  #       # end
  #       # case :couchbeam.stream_attachment(db, doc_id, attachment_name, content_type, stream) do
  #       #   :ok ->
  #       #     IO.puts("Attachment uploaded successfully!")
  #       #     :ok

  #       #   {:error, reason} ->
  #       #     IO.puts("Failed to upload attachment: #{inspect(reason)}")
  #       #     {:error, reason}
  #       # end
  #     end

  #   {:reply, reply,state}
  # end

  def handle_call({:tick_num, db, doc, key}, _from, state) do
    reply =
      with {:ok, db} <- :couchbeam.open_db(state.conn, db),
           {:ok, doc} <- :couchbeam.open_doc(db, doc) do
        case is_integer(num = :couchbeam_doc.get_value(key, doc)) do
          true ->
            tick = num + 1
            doc = :couchbeam_doc.set_value(key, tick, doc)
            :couchbeam.save_doc(db, doc)
            tick

          _ ->
            {:error, "not a number"}
        end

        # :couchbeam.save_doc(db, doc)
      end

    {:reply, reply, state}
  end

  def handle_call({:save_doc, db, doc}, _from, state) do
    reply =
      with {:ok, db} <- :couchbeam.open_db(state.conn, db) do
        :couchbeam.save_doc(db, doc)
      end

    {:reply, reply, state}
  end

  def handle_call({:save_attachment, db, attachment}, _from, state) do
    reply =
      with {:ok, db} <- :couchbeam.open_db(state.conn, db) do
        :couchbeam.put_attachment(
          db,
          attachment.doc_id,
          attachment.name,
          attachment.data,
          [%{content_type: attachment.content_type}]
        )
      end

    {:reply, reply, state}
  end


    def handle_call({:save_attachment, db, doc_id, name, data}, _from, state) do
    reply =
      with {:ok, db} <- :couchbeam.open_db(state.conn, db) do
        :couchbeam.put_attachment(
          db,
          doc_id,
          name,
          data,
          [%{content_type: "application/octet-stream"}]
        )
      end

    {:reply, reply, state}
  end


  def handle_call({:update_attachment, db, doc_id, attachment}, _from, state) do
    reply =
      with {:ok, db} <- :couchbeam.open_db(state.conn, db),
           {:ok, doc} <- :couchbeam.open_doc(db, doc_id),
           #  latest_rev = get_rev_from_erlang_doc(erlang_doc) do
           # IO.puts("latest_rev: #{inspect(latest_rev)}")
           doc =
             :couchbeam_attachments.add_inline(
               doc,
               attachment.data,
               attachment.name,
               attachment.content_type
             ) do
        :couchbeam.save_doc(db, doc)
      end

    {:reply, reply, state}
  end

    def handle_call({:update_attachment, db, doc_id, name, data}, _from, state) do
    reply =
      with {:ok, db} <- :couchbeam.open_db(state.conn, db),
           {:ok, doc} <- :couchbeam.open_doc(db, doc_id),
           #  latest_rev = get_rev_from_erlang_doc(erlang_doc) do
           # IO.puts("latest_rev: #{inspect(latest_rev)}")
           doc =
             :couchbeam_attachments.add_inline(
               doc,
               data,
               name,
               "application/octet-stream"
             ) do
        :couchbeam.save_doc(db, doc)
      end

    {:reply, reply, state}
  end

  def handle_call({:delete_doc, db, doc}, _from, state) do
    reply =
      with {:ok, db} <- :couchbeam.open_db(state.conn, db) do
        :couchbeam.delete_doc(db, doc)
      end

    {:reply, reply, state}
  end

  def handle_call({:delete_value, db, doc, key}, _from, state) do
    reply =
      with {:ok, db} <- :couchbeam.open_db(state.conn, db),
           {:ok, doc} <- :couchbeam.open_doc(db, doc),
           doc <- :couchbeam_doc.delete_value(key, doc) do
        :couchbeam.save_doc(db, doc)
      end

    {:reply, reply, state}
  end

  @impl true
  def handle_info({:EXIT, _pid, :normal}, state) do
    # 忽略正常退出
    {:noreply, state}
  end

  # defp get_rev_from_erlang_doc({doc_props}) do
  #   case List.keyfind(doc_props, "_rev", 0) do
  #     {"_rev", rev} -> rev
  #     nil -> nil
  #   end
  # end
end
